Skip to content
On this page

新手篇-熟悉 NestJS


前言

经过了需求分析以及技术选型之后,我们正式步入了第三个环节:脚手架搭建

工欲善其事,必先利其器NestJS 为开发者提供了很多开箱即用的功能,我们可以根据团队的需求搭建一套适配所有业务开发的基础脚手架。所以接下来的 2 章是基础篇的教学,将带领大家逐步的学习与搭建一套基础业务脚手架,便于后期快速开发业务。

本章的内容比较基础,如果使用过 NestJs 的同学或者对 IoC 模式熟悉的同学可以快速略过。

控制反转 IoC

在之前的介绍中有提到,NestJS 作为开发体验上最接近于传统后端的开发框架,其中最大的相同点就是 IoC,也就是 JAVA 中经常提到的控制反转依赖注入

在接下去使用 NestJS 的开发过程中会大量接触到 IoC 模式,所以先对 IoC 做一个简单概念解析,了解一下什么是 IoC,以及为什么要使用 IoC

控制反转(Inversion of Control,缩写为 IoC)是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

如果学过 JAVA 的同学应该会比较熟悉,但如果是前端同学刚刚接触的话,可能会比较陌生,一时间难以上手。纯文字版本的解释难免晦涩,接下来我们用一个简单的小列子来解释 IoC 容器的使用:

javascript
class A {
  constructor(params) {
    this.params = params
  }
}

class B extends A {
  constructor(params) {
    super(params)
  }
  run() {
    console.log(this.params);
  }
}

new B('hello').run();

我们可以看到,B 中代码的实现是需要依赖 A 的,两者的代码耦合度非常高。当两者之间的业务逻辑复杂程度增加的情况下,维护成本与代码可读性都会随着增加,并且很难再多引入额外的模块进行功能拓展

为了解决这个情况,我们可以引入一个 IoC 容器:

javascript
class A {
  constructor(params) {
    this.params = params
  }
}

class C {
  constructor(params) {
    this.params = params
  }
}

class Container {
  constructor() { this.modules = {} }

  provide(key, object) { this.modules[key] = object }

  get(key) { return this.modules[key] }
}

const mo = new Container();

mo.provide('a', new A('hello'))
mo.provide('c', new C('world'))

class B {
  constructor(container) {
    this.a = container.get('a');
    this.c = container.get('c');
  }
  run() {
    console.log(this.a.params + ' ' + this.c.params)
  }
}

new B(mo).run();

如上述代码所示,在引入 IoC 容器 container 之后,BA 的代码逻辑已经解耦,可以单独拓展其他功能,也可以方便地加入其他模块 C。所以在面对复杂的后端业务逻辑中,引入 IoC 可以降低组件之间的耦合度,实现系统各层之间的解耦,减少维护与理解成本。

当然,上述的 Demo 只是一个非常简单的例子,实际开发过程中场景远比 Demo 更加复杂。

Nest CLI

与所有的主流语言一样,NestJs 也有自己的 Nest CLI 工具,除了提供创建基础模板的功能之外,额外提供了很多方便的功能。

与前端项目的开发模式不同,在后端业务开发中存在着大量可复用或者有规则的模块,善于使用 CLI 可以帮助我们节约大量的重复工作,现在我们来一起学一下 CLI 的运用。首先看下 CLI 提供了多少的功能:

$ nest --help

image.png

如上图所示,运行完 help 命令之后,可以使用 generate 便捷地生成常用文件,例如超频使用Controller 以及 Sevice 的文件等。

使用规则

除了 nest \--help 查看全局命令之外,运行nest <command> \--help 可以查看特定于命令的选项。

image.png

命令别名描述
newn搭建一个新的标准模式应用程序,包含所有需要运行的样板文件。
generateg根据原理图生成或修改文件。

通用的命令选项

选项描述
--dry-run报告将要进行的更改,但不更改文件系统,别名: -d。
--skip-git跳过 git 存储库初始化,别名: -g。
--skip-install跳过软件包安装,别名:-s。
--package-manager [package-manager]指定包管理器,使用 npm 或 yarn,必须全局安装包管理器,别名: -p。
--language [language]指定编程语言(TS 或 JS),别名: -l。
--collection [collectionName]指定逻辑示意图集合,使用已安装的包含原理的 npm 软件包的软件包名称,别名:-c。

在常规项目中,使用创建模板和文件这两个命令最多,所以小册只列举了这两个功能,如果你想了解更多的 CLI 功能可以直接查看源文档

配置规则

直接通过 CLI 创建的项目根路径下会自动生成一个 nest-cli.json 配置文件:

image.png

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "root": "src"
}

默认生成的配置文件有如上一些属性:

配置属性属性描述
collection用于配置生成部件的 schematics 组合的点,一般无需修改
sourceRoot默认项目根目录
------
compilerOptions编译选项与设置
generateOptions全局生成的选项和选项的设置
monorepo启用 monorepo
projectmonorepo 模式结构项目配置
------
assets额外文件类型资源处理,非 TS 与 JS 类型
watchAssets是否使用 watch 模式来监控指定资源文件

monorepo 模式开发有它的优点,如果是个人维护或者是关联性比较高的项目可以尝试使用 monorepo 来开发项目,但是小册选择的网关项目拆出的三个模块虽然有一定的关系,但物料以及用户系统同时还会与 DevOps 等其他系统有关联,所以会使用 multirepo 维护三个不同的项目,以微服务的模式关联各个模块功能。

创建项目工程模板

在查看完 Nest CLI 的常用命令之后,可以使用以下命令快速创建一个简单的工程模板:

$ npm i -g @nestjs/cli
$ nest new gateway

image.png

项目文件介绍

除去配置常见的配置文件之外,在 src 目录下有一些 Nestjs 标准的文件规范:

src
 ├── app.controller.spec.ts
 ├── app.controller.ts
 ├── app.module.ts
 ├── app.service.ts
 └── main.ts
文件名文件描述
app.controller.ts常见功能是用来处理 http 请求以及调用 service 层的处理方法
app.module.ts根模块用于处理其他类的引用与共享。
app.service.ts封装通用的业务逻辑、与数据层的交互(例如数据库)、其他额外的一些三方请求
main.ts应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。

在后续开发项目的过程中,使用约定俗成的 name.[type] 规则来创建对应的类型文件,便于查找对应的模块。

第一个 http 请求

再依赖安装完毕之后,可以使用如下命令启动 NestJS 应用,然后浏览器即可访问 http://localhost:3000/ :出现如下界面即代表项目已经正常启动了。

$ npm run start

image.png

服务正常启动之后,接下来我们要开始写下第一个功能【用户模块】。

首先运行如下命令,CLI 会快速帮助我们自动生成一个用户的 UserController

$ nest g co user

不过此命令同时也会生成后缀为 spec 的测试文件,虽然有测试功能非常好,但在快速开发过程中,并非每一个功能都需要自动化测试覆盖,只要保证主要的功能有用例覆盖即可。

image.png

如果不需要每一次生成 spec 文件,可以在根目录下的 nest-cli.json 添加如下配置,禁用测试用例生成,后续再使用 CLI 创建 Controller 或者 Service 类型文件的时候,将不会继续生成:

  "generateOptions": {
    "spec": false
  }

image.png

回归正题,再创建 UserController 的同时 CLI 也会自动在 app.module.ts 里面帮我们注册好 Controller。整个过程非常简便,只要在 UserController 写下第一个 http 请求即可。

import { Controller, Get } from '@nestjs/common';

@Controller('user')
export class UserController {
  @Get()
  getHello(): string {
    return 'hello, world!';
  }
}

等待程序重新编译运行完毕之后,在浏览器输入 http://localhost:3000/user 访问即可看到:【你好,世界!

image.png

第一个 CURD

在小试牛刀之后,下面我们要开始借助 CLI 的能力快速生成 CURD 模块:

  • 生成一个模块 (nest g mo) 来组织代码,使其保持清晰的界限(Module)。
  • 生成一个控制器 (nest g co) 来定义CRUD路径(Controller)。
  • 生成一个服务 (nest g s) 来表示/隔离业务逻辑(Service)。
  • 生成一个实体类/接口来代表资源数据类型(Entity)。

可以看出一个最简单的 CURD 涉及的模块也会非常多(至少需要以上四个模块才能完成一个基础的 CURD 功能),并且要运行多个命令才能得到想要的结果,所幸 Nest CLI 已经集成了这样的功能来帮助我们减少重复的工作量:

$ nest g resource user 

image.png

第一次使用这个命令的时候,除了生成文件之外还会自动使用 npm 帮我们更新资源,安装一些额外的插件,后续再次使用就不会更新了。

image.png

安装依赖之后可以看到,我们借助了 Nest CLI 快速的生成了一套标准的 CURD 模块甚至 dto 文件也一并生成了,后续只需要更新用户模块的业务逻辑即可。

写在最后

本章主要是介绍了 IoC 设计模式以及如何借助 CLI 创建了简单的工程模板与 CURD 模块。可以看到 Nest CLI 对比其他一些 CLI 工具,在针对开发功能优化这块做得非常不错,特别是模块生成跟自动注册这块逻辑。不过,也是基于后端有一套规则可循,这些功能才能实现,这也正是前后端项目不太一样的地方。

虽然 NestJs 提供了一个简单的工程模板,但这个模板离实际可用的工程差距还有点大,接下来将与大家一起逐步添加对应的功能,使之达到一个符合实际项目开发要求的模板。

基础篇的内容大部分都是围绕着 NestJS 提供的功能模块开发,所以有一些细节的部分可以参考 NestJS 的英文文档一起阅读,小册中使用到的部分会尽可能讲解得详细一点。

如果你有什么疑问,欢迎在评论区提出或者加群沟通。 👏